第28天用昨日寫好的Java檔案,當作終端指令創建管理者帳號後,用新建的帳號來做管理後台的登入驗證
現在回到懷念重複的操作 打開SecurityConf.java,在請求驗證鍊新增管理者專用路由,排除後台登入
authorize
// 管理者登入不做驗證
.requestMatchers("/api/admin/login").permitAll()
// api/admin/xxx 的請求,除了login都需經過驗證
.requestMatchers("/api/admin/**").authenticated()
.requestMatchers("/api/member/login").permitAll()
.requestMatchers("/api/member/register").permitAll()
.requestMatchers("/api/member/**").authenticated()
.anyRequest().permitAll();
在新增 AdminDetialService
@Service("AdminDetailServ")
public class AdminUserDetailsService implements UserDetailsService {
@Autowired
private AdminDao adminMapper;
@Override
public UserDetails loadUserByUsername(String account) throws UsernameNotFoundException {
Admin admin = adminMapper.getByAccount(account);
if (admin == null) {
throw new UsernameNotFoundException("Admin Not Found");
}
System.out.printf(
"從 管理者帳號 %s 得到 User ID %d\n",
account, admin.getId());
return new User(
admin.getAccount(),
admin.getPassword(),
Collections.emptyList());
}
}
建立 AdminController 開後台登入API
// AdminController.java
/**
* 後台登入API
*
* @return ResponseEntity<Object>
*/
@PostMapping("/login")
public ResponseEntity<Object> login(
@RequestBody LoginAdmin loginAdmin
) {
try {
// 登入同樣用 AuthenticationManager 進行帳號登入驗證
Authentication authentication = new UsernamePasswordAuthenticationToken(
loginAdmin.getAccount(), loginAdmin.getPassword());
authenticationManager.authenticate(authentication);
// 另外自定義 Jwt Payload
Map payload = new HashMap<>();
payload.put("stage", "dashboard");
String logingToken = jwtUtil.generateToken(loginAdmin.getAccount(), payload);
return ResponseEntity.status(HttpStatus.OK).body(Map.of(
"status", true,
"token", logingToken));
} catch (Exception ex) {
System.out.println(ex.getMessage());
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(Map.of(
"status", false,
"token", ""));
}
}
運行 spring-boot run 會拋出例外
原因是掃描到到多個實作 UserDetailsService 介面的元件,才造成此異常,為了讓 Interface 可以注入到Bean元件中,需要注入Bean元件的部分,要利用 @Qualifier 指定使用的Bean元件別名
先在UserDetailsService實做類別添加別名
// com.app.demo.services.AdminUserDetailsService.java
@Service("AdminDetailServ")
public class AdminUserDetailsService implements UserDetailsService{
...略
}
// com.app.demo.services.DBUserDetailsService.java
@Service("MemberDetailServ")
public class DBUserDetailsService implements UserDetailsService {
...略
}
接著點開在 SecurityConf 欄位使用 @Qualifier 指定別名注入 UserDetailsService介面的欄位
// com.app.demo.conf.SecurityConf.java
@Autowired
@Qualifier("MemberDetailServ")
public UserDetailsService dbUserService;@Autowired
@Autowired
@Qualifier("AdminDetailServ")
private UserDetailsService adminService;
// 添加依賴元件 HttpServletRequest 取得當前請求資訊
@Autowired
private HttpServletRequest request;
// 添加新方法,實作並根據請求 uri前綴取得相應UserDetailsService 實作
@Bean
public UserDetailsService userDetailsService() {
return username -> {
String uri = request.getRequestURI();
// 利用請求 Uri 判斷驗證對象來源
if (uri.startsWith("/api/admin")) {
return adminService.loadUserByUsername(username);
}
return dbUserService.loadUserByUsername(username);
};
}
@Bean
public AuthenticationProvider daoAuthenticationProvider() {
DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
provider.setPasswordEncoder(passwordEncoder());
// 原本直接套用 dbUserService 實例,這次透過方法彈性改動 UserDetailsService
provider.setUserDetailsService(userDetailsService());
return provider;
}
在重新執行啟用Spring Boot伺服器,個別從 member跟 admin個別的登入端點登入
會根據 userDetailsService()的邏輯,從不同來源取得要驗證的用戶
最後在調整過濾器 UserAuthFilter.java 原本只有
// 原本注入的 DBUserDetailsService 改用 Qualifier注入
@Autowired
@Qualifier("MemberDetailServ")
private UserDetailsService userDetialServ;
// 使用 AdminDetailServ 注入 AdminUserDetailsService
@Autowired
@Qualifier("AdminDetailServ")
private UserDetailsService adminDetialServ;
// doFilterInternal 部分內容調整,根據 stage 判斷查找前台還是後台使用者
UserDetails userDetials = null;
String stage = payload.get("stage", String.class);
System.out.printf("Claims stage is: %s\n", stage);
// 管理者帳號用 adminDetialServ 取得User
if (stage.equals("dashboard")) {
userDetials = adminDetialServ.loadUserByUsername(account);
} else {
userDetials = userDetialServ.loadUserByUsername(account);
}
System.out.printf("UserDetails Username %s\n", userDetials.getUsername());
利用登入管理者後台拿到的Token,在隨意請求 /api/admin為前綴的端點,原本403拒絕請求,現在則變成404
今日進度完成,明天來重構 過濾器的部分並在請求添加 Role驗證